package k8s

import (
	"context"
	"fmt"

	"v8s-controller/internal"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	networkingv1 "k8s.io/api/networking/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes/scheme"
	typeappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
	typecorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
	typenetworkingv1 "k8s.io/client-go/kubernetes/typed/networking/v1"
)

type (
	Client interface {
		AppsV1() typeappsv1.AppsV1Interface
		CoreV1() typecorev1.CoreV1Interface
		NetworkingV1() typenetworkingv1.NetworkingV1Interface
	}

	Visitor interface {
		visitNamespace(context.Context, *corev1.Namespace) error
		visitDeployment(context.Context, *appsv1.Deployment) error
		visitNetworkPolicy(context.Context, *networkingv1.NetworkPolicy) error
		visitService(context.Context, *corev1.Service) error
		visitSecret(context.Context, *corev1.Secret) error
		visitVolumeClaim(context.Context, *corev1.PersistentVolumeClaim) error
		visitResourceQuota(context.Context, *corev1.ResourceQuota) error
		visitLimitRange(context.Context, *corev1.LimitRange) error
	}

	KindVisitor interface {
		visitNamespace(context.Context) error
		visitDeployment(context.Context) error
		visitNetworkPolicy(context.Context) error
		visitService(context.Context) error
		visitSecret(context.Context) error
		visitVolumeClaim(context.Context) error
		visitResourceQuota(context.Context) error
		visitLimitRange(context.Context) error
	}

	DescribableResource struct {
		object   runtime.Object
		accessor metav1.Object
	}

	DescribableResourceIdentity string

	DescribableResources []*DescribableResource

	IndexedDescribableResources map[DescribableResourceIdentity]*DescribableResource

	creatingVisitor struct {
		client    Client
		namespace internal.Namespace
	}

	listVisitor struct {
		client    Client
		namespace internal.Namespace

		resources []*DescribableResource
	}

	updatingVisitor struct {
		client    Client
		namespace internal.Namespace
	}

	deletingVisitor struct {
		client    Client
		namespace internal.Namespace
	}

	getVisitor struct {
		resourceName string
		resource     *DescribableResource
		client       Client
		namespace    internal.Namespace
	}

	Kind string
)

const (
	NamespaceKind     Kind = "Namespace"
	ServiceKind       Kind = "Service"
	DeploymentKind    Kind = "Deployment"
	NetworkPolicyKind Kind = "NetworkPolicy"
	SecretKind        Kind = "Secret"
	ResourceQuotaKind Kind = "ResourceQuota"
	VolumeClaimKind   Kind = "PersistentVolumeClaim"
	LimitRangeKind    Kind = "LimitRange"
)

func CreateResource(ctx context.Context, client Client, namespace internal.Namespace, object *DescribableResource) error {
	return visitResource(ctx, &creatingVisitor{
		client:    client,
		namespace: namespace,
	}, object)
}

func UpdateResource(ctx context.Context, client Client, namespace internal.Namespace, object *DescribableResource) error {
	return visitResource(ctx, &updatingVisitor{
		client:    client,
		namespace: namespace,
	}, object)
}

func ListNamespaces(ctx context.Context, client Client) ([]*corev1.Namespace, error) {
	visitor := &listVisitor{
		client:    client,
		namespace: "",
		resources: make([]*DescribableResource, 0),
	}

	if err := visitKind(ctx, visitor, NamespaceKind); err != nil {
		return nil, err
	}

	namespaces := make([]*corev1.Namespace, 0, len(visitor.resources))

	for _, resource := range visitor.resources {
		current := resource.object.(*corev1.Namespace)
		namespaces = append(namespaces, current)
	}

	return namespaces, nil
}

func ListAllSupportedResources(ctx context.Context, client Client, namespace internal.Namespace) (DescribableResources, error) {
	return ListResources(ctx, client, namespace,
		ServiceKind,
		DeploymentKind,
		NetworkPolicyKind,
		SecretKind,
		VolumeClaimKind,
		ResourceQuotaKind,
		LimitRangeKind,
	)
}

func ListResources(ctx context.Context, client Client, namespace internal.Namespace, kinds ...Kind) (DescribableResources, error) {
	visitor := &listVisitor{
		client:    client,
		namespace: namespace,
		resources: make(DescribableResources, 0),
	}

	for _, kind := range kinds {
		if err := visitKind(ctx, visitor, kind); err != nil {
			return nil, err
		}
	}

	return visitor.resources, nil
}

func DeleteNamespace(ctx context.Context, client Client, namespace internal.Namespace) error {
	resource, err := ToDescribableResource(&corev1.Namespace{})
	if err != nil {
		return fmt.Errorf("failed to create describable resource: %w", err)
	}

	return visitResource(ctx, &deletingVisitor{
		client:    client,
		namespace: namespace,
	}, resource)
}

func DeleteResource(ctx context.Context, client Client, namespace internal.Namespace, object *DescribableResource) error {
	return visitResource(ctx, &deletingVisitor{
		client:    client,
		namespace: namespace,
	}, object)
}

func GetResource(ctx context.Context, client Client, namespace internal.Namespace, kind Kind, name string) (*DescribableResource, error) {
	visitor := &getVisitor{
		resourceName: name,
		client:       client,
		namespace:    namespace,
	}

	err := visitKind(ctx, visitor, kind)
	if err != nil {
		return nil, err
	}

	return visitor.resource, nil
}

// ==================================================================================================
// ========================================== Visitors ==============================================
// ==================================================================================================

func visitResource(ctx context.Context, visitor Visitor, resource *DescribableResource) error {
	switch typed := resource.object.(type) {
	case *corev1.Namespace:
		return visitor.visitNamespace(ctx, typed)
	case *appsv1.Deployment:
		return visitor.visitDeployment(ctx, typed)
	case *corev1.Service:
		return visitor.visitService(ctx, typed)
	case *networkingv1.NetworkPolicy:
		return visitor.visitNetworkPolicy(ctx, typed)
	case *corev1.Secret:
		return visitor.visitSecret(ctx, typed)
	case *corev1.PersistentVolumeClaim:
		return visitor.visitVolumeClaim(ctx, typed)
	case *corev1.ResourceQuota:
		return visitor.visitResourceQuota(ctx, typed)
	case *corev1.LimitRange:
		return visitor.visitLimitRange(ctx, typed)
	default:
		return fmt.Errorf("object kind '%s' is not supported yet", resource.Kind())
	}
}

func visitKind(ctx context.Context, visitor KindVisitor, kind Kind) error {
	switch kind {
	case NamespaceKind:
		return visitor.visitNamespace(ctx)
	case DeploymentKind:
		return visitor.visitDeployment(ctx)
	case ServiceKind:
		return visitor.visitService(ctx)
	case NetworkPolicyKind:
		return visitor.visitNetworkPolicy(ctx)
	case SecretKind:
		return visitor.visitSecret(ctx)
	case VolumeClaimKind:
		return visitor.visitVolumeClaim(ctx)
	case ResourceQuotaKind:
		return visitor.visitResourceQuota(ctx)
	case LimitRangeKind:
		return visitor.visitLimitRange(ctx)
	default:
		return fmt.Errorf("kind '%s' is not supported yet", kind)
	}
}

// ==================================================================================================
// ========================================= Describable ============================================
// ==================================================================================================

func ToDescribableResource(object runtime.Object) (*DescribableResource, error) {
	if object.GetObjectKind().GroupVersionKind().Empty() {
		gvks, _, err := scheme.Scheme.ObjectKinds(object)
		if err != nil {
			return nil, fmt.Errorf("cannot determine gvk of object: %w", err)
		}

		for _, gvk := range gvks {
			if len(gvk.Kind) == 0 {
				continue
			}
			if len(gvk.Version) == 0 || gvk.Version == runtime.APIVersionInternal {
				continue
			}

			object.GetObjectKind().SetGroupVersionKind(gvk)
			break
		}

		if object.GetObjectKind().GroupVersionKind().Empty() {
			return nil, fmt.Errorf("empty kind of resource")
		}
	}

	accessor, err := meta.Accessor(object)
	if err != nil {
		return nil, fmt.Errorf("unsupported kind '%s'", object.GetObjectKind().GroupVersionKind().Kind)
	}

	return &DescribableResource{
		object:   object,
		accessor: accessor,
	}, nil
}

func ToDescribableResources(objects []runtime.Object) (DescribableResources, error) {
	results := make([]*DescribableResource, 0, len(objects))

	for _, object := range objects {
		current := object
		resource, err := ToDescribableResource(current)
		if err != nil {
			return nil, err
		}

		results = append(results, resource)
	}

	return results, nil
}

func (r *DescribableResources) Indexed() IndexedDescribableResources {
	results := IndexedDescribableResources{}

	for _, resource := range *r {
		current := resource
		results[resource.Identity()] = current
	}

	return results
}

func (m IndexedDescribableResources) RemoveResource(resource *DescribableResource) (removed *DescribableResource) {
	identity := resource.Identity()

	removed = m[identity]
	if removed != nil {
		delete(m, identity)
	}

	return removed
}

func (r *DescribableResource) Resource() runtime.Object {
	return r.object
}

func (r *DescribableResource) Kind() Kind {
	return Kind(r.object.GetObjectKind().GroupVersionKind().Kind)
}

func (r *DescribableResource) Name() string {
	return r.accessor.GetName()
}

func (r *DescribableResource) Namespace() string {
	if r.Kind() == NamespaceKind {
		return r.Name()
	}
	return r.accessor.GetNamespace()
}

func (r *DescribableResource) IsKind(kind Kind) bool {
	return r.Kind() == kind
}

func (r *DescribableResource) Identity() DescribableResourceIdentity {
	return DescribableResourceIdentity(fmt.Sprintf("%s:%s", r.Kind(), r.Name()))
}

// ==================================================================================================
// ============================================ Create ==============================================
// ==================================================================================================

func (c *creatingVisitor) visitNamespace(ctx context.Context, namespace *corev1.Namespace) error {
	_, err := c.client.CoreV1().Namespaces().Create(ctx, namespace, metav1.CreateOptions{})
	return err
}

func (c *creatingVisitor) visitDeployment(ctx context.Context, deployment *appsv1.Deployment) error {
	_, err := c.client.AppsV1().Deployments(string(c.namespace)).Create(ctx, deployment, metav1.CreateOptions{})
	return err
}

func (c *creatingVisitor) visitNetworkPolicy(ctx context.Context, policy *networkingv1.NetworkPolicy) error {
	_, err := c.client.NetworkingV1().NetworkPolicies(string(c.namespace)).Create(ctx, policy, metav1.CreateOptions{})
	return err
}

func (c *creatingVisitor) visitService(ctx context.Context, service *corev1.Service) error {
	_, err := c.client.CoreV1().Services(string(c.namespace)).Create(ctx, service, metav1.CreateOptions{})
	return err
}

func (c *creatingVisitor) visitSecret(ctx context.Context, secret *corev1.Secret) error {
	_, err := c.client.CoreV1().Secrets(string(c.namespace)).Create(ctx, secret, metav1.CreateOptions{})
	return err
}

func (c *creatingVisitor) visitVolumeClaim(ctx context.Context, pvc *corev1.PersistentVolumeClaim) error {
	_, err := c.client.CoreV1().PersistentVolumeClaims(string(c.namespace)).Create(ctx, pvc, metav1.CreateOptions{})
	return err
}

func (c *creatingVisitor) visitResourceQuota(ctx context.Context, quota *corev1.ResourceQuota) error {
	_, err := c.client.CoreV1().ResourceQuotas(string(c.namespace)).Create(ctx, quota, metav1.CreateOptions{})
	return err
}

func (c *creatingVisitor) visitLimitRange(ctx context.Context, limitRange *corev1.LimitRange) error {
	_, err := c.client.CoreV1().LimitRanges(string(c.namespace)).Create(ctx, limitRange, metav1.CreateOptions{})
	return err
}

// ==================================================================================================
// ============================================ Update ==============================================
// ==================================================================================================

func (u *updatingVisitor) visitNamespace(ctx context.Context, namespace *corev1.Namespace) error {
	_, err := u.client.CoreV1().Namespaces().Update(ctx, namespace, metav1.UpdateOptions{})
	return err
}

func (u *updatingVisitor) visitDeployment(ctx context.Context, deployment *appsv1.Deployment) error {
	_, err := u.client.AppsV1().Deployments(string(u.namespace)).Update(ctx, deployment, metav1.UpdateOptions{})
	return err
}

func (u *updatingVisitor) visitNetworkPolicy(ctx context.Context, policy *networkingv1.NetworkPolicy) error {
	_, err := u.client.NetworkingV1().NetworkPolicies(string(u.namespace)).Update(ctx, policy, metav1.UpdateOptions{})
	return err
}

func (u *updatingVisitor) visitService(ctx context.Context, service *corev1.Service) error {
	_, err := u.client.CoreV1().Services(string(u.namespace)).Update(ctx, service, metav1.UpdateOptions{})
	return err
}

func (u *updatingVisitor) visitSecret(ctx context.Context, secret *corev1.Secret) error {
	_, err := u.client.CoreV1().Secrets(string(u.namespace)).Update(ctx, secret, metav1.UpdateOptions{})
	return err
}

func (u *updatingVisitor) visitVolumeClaim(ctx context.Context, pvc *corev1.PersistentVolumeClaim) error {
	_, err := u.client.CoreV1().PersistentVolumeClaims(string(u.namespace)).Update(ctx, pvc, metav1.UpdateOptions{})
	return err
}
func (u *updatingVisitor) visitResourceQuota(ctx context.Context, quota *corev1.ResourceQuota) error {
	_, err := u.client.CoreV1().ResourceQuotas(string(u.namespace)).Update(ctx, quota, metav1.UpdateOptions{})
	return err
}

func (u *updatingVisitor) visitLimitRange(ctx context.Context, limitRange *corev1.LimitRange) error {
	_, err := u.client.CoreV1().LimitRanges(string(u.namespace)).Update(ctx, limitRange, metav1.UpdateOptions{})
	return err
}

// ==================================================================================================
// ============================================ Delete ==============================================
// ==================================================================================================

func (d *deletingVisitor) visitNamespace(ctx context.Context, namespace *corev1.Namespace) error {
	return d.client.CoreV1().Namespaces().Delete(ctx, string(d.namespace), metav1.DeleteOptions{})
}

func (d *deletingVisitor) visitDeployment(ctx context.Context, deployment *appsv1.Deployment) error {
	return d.client.AppsV1().Deployments(string(d.namespace)).Delete(ctx, deployment.Name, metav1.DeleteOptions{})
}

func (d *deletingVisitor) visitNetworkPolicy(ctx context.Context, policy *networkingv1.NetworkPolicy) error {
	return d.client.NetworkingV1().NetworkPolicies(string(d.namespace)).Delete(ctx, policy.Name, metav1.DeleteOptions{})
}

func (d *deletingVisitor) visitService(ctx context.Context, service *corev1.Service) error {
	return d.client.CoreV1().Services(string(d.namespace)).Delete(ctx, service.Name, metav1.DeleteOptions{})
}

func (d *deletingVisitor) visitSecret(ctx context.Context, secret *corev1.Secret) error {
	return d.client.CoreV1().Secrets(string(d.namespace)).Delete(ctx, secret.Name, metav1.DeleteOptions{})
}

func (d *deletingVisitor) visitVolumeClaim(ctx context.Context, pvc *corev1.PersistentVolumeClaim) error {
	return d.client.CoreV1().PersistentVolumeClaims(string(d.namespace)).Delete(ctx, pvc.Name, metav1.DeleteOptions{})
}

func (d *deletingVisitor) visitResourceQuota(ctx context.Context, quota *corev1.ResourceQuota) error {
	return d.client.CoreV1().Secrets(string(d.namespace)).Delete(ctx, quota.Name, metav1.DeleteOptions{})
}

func (d *deletingVisitor) visitLimitRange(ctx context.Context, limitRange *corev1.LimitRange) error {
	return d.client.CoreV1().LimitRanges(string(d.namespace)).Delete(ctx, limitRange.Name, metav1.DeleteOptions{})
}

// ==================================================================================================
// ============================================= List ===============================================
// ==================================================================================================

func (l *listVisitor) visitNamespace(ctx context.Context) error {
	namespaces, err := l.client.CoreV1().Namespaces().List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}

	for _, namespace := range namespaces.Items {
		current := namespace

		resource, err := ToDescribableResource(&current)
		if err != nil {
			return err
		}

		l.resources = append(l.resources, resource)
	}

	return nil
}

func (l *listVisitor) visitDeployment(ctx context.Context) error {
	deployments, err := l.client.AppsV1().Deployments(string(l.namespace)).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}

	for _, deployment := range deployments.Items {
		current := deployment
		resource, err := ToDescribableResource(&current)
		if err != nil {
			return err
		}

		l.resources = append(l.resources, resource)
	}

	return nil
}

func (l *listVisitor) visitNetworkPolicy(ctx context.Context) error {
	policies, err := l.client.NetworkingV1().NetworkPolicies(string(l.namespace)).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}

	for _, policy := range policies.Items {
		current := policy
		resource, err := ToDescribableResource(&current)
		if err != nil {
			return err
		}

		l.resources = append(l.resources, resource)
	}

	return nil
}

func (l *listVisitor) visitService(ctx context.Context) error {
	services, err := l.client.CoreV1().Services(string(l.namespace)).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}

	for _, service := range services.Items {
		current := service
		resource, err := ToDescribableResource(&current)
		if err != nil {
			return err
		}

		l.resources = append(l.resources, resource)
	}

	return nil
}

func (l *listVisitor) visitSecret(ctx context.Context) error {
	secrets, err := l.client.CoreV1().Secrets(string(l.namespace)).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}

	for _, secret := range secrets.Items {
		current := secret
		resource, err := ToDescribableResource(&current)
		if err != nil {
			return err
		}

		l.resources = append(l.resources, resource)
	}

	return nil
}

func (l *listVisitor) visitVolumeClaim(ctx context.Context) error {
	volumes, err := l.client.CoreV1().PersistentVolumeClaims(string(l.namespace)).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}
	for _, volume := range volumes.Items {
		current := volume
		resource, err := ToDescribableResource(&current)
		if err != nil {
			return err
		}

		l.resources = append(l.resources, resource)
	}

	return nil
}

func (l *listVisitor) visitResourceQuota(ctx context.Context) error {
	quotas, err := l.client.CoreV1().ResourceQuotas(string(l.namespace)).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}
	for _, quota := range quotas.Items {
		current := quota
		resource, err := ToDescribableResource(&current)
		if err != nil {
			return err
		}

		l.resources = append(l.resources, resource)
	}

	return nil
}

func (l *listVisitor) visitLimitRange(ctx context.Context) error {
	ranges, err := l.client.CoreV1().LimitRanges(string(l.namespace)).List(ctx, metav1.ListOptions{})
	if err != nil {
		return err
	}

	for _, limitRange := range ranges.Items {
		current := limitRange
		resource, err := ToDescribableResource(&current)
		if err != nil {
			return err
		}

		l.resources = append(l.resources, resource)
	}

	return nil
}

// ==================================================================================================
// ============================================== Get ===============================================
// ==================================================================================================

func (g *getVisitor) visitNamespace(ctx context.Context) error {
	resource, err := g.client.CoreV1().Namespaces().Get(ctx, string(g.namespace), metav1.GetOptions{})
	if err != nil {
		return err
	}

	describable, err := ToDescribableResource(resource)
	if err != nil {
		return err
	}

	g.resource = describable
	return nil
}

func (g *getVisitor) visitDeployment(ctx context.Context) error {
	resource, err := g.client.AppsV1().Deployments(string(g.namespace)).Get(ctx, g.resourceName, metav1.GetOptions{})
	if err != nil {
		return err
	}

	describable, err := ToDescribableResource(resource)
	if err != nil {
		return err
	}

	g.resource = describable
	return nil
}

func (g *getVisitor) visitNetworkPolicy(ctx context.Context) error {
	resource, err := g.client.NetworkingV1().NetworkPolicies(string(g.namespace)).Get(ctx, g.resourceName, metav1.GetOptions{})
	if err != nil {
		return err
	}

	describable, err := ToDescribableResource(resource)
	if err != nil {
		return err
	}

	g.resource = describable
	return nil
}

func (g *getVisitor) visitService(ctx context.Context) error {
	resource, err := g.client.CoreV1().Services(string(g.namespace)).Get(ctx, g.resourceName, metav1.GetOptions{})
	if err != nil {
		return err
	}

	describable, err := ToDescribableResource(resource)
	if err != nil {
		return err
	}

	g.resource = describable
	return nil
}

func (g *getVisitor) visitSecret(ctx context.Context) error {
	resource, err := g.client.CoreV1().Secrets(string(g.namespace)).Get(ctx, g.resourceName, metav1.GetOptions{})
	if err != nil {
		return err
	}

	describable, err := ToDescribableResource(resource)
	if err != nil {
		return err
	}

	g.resource = describable
	return nil
}

func (g *getVisitor) visitVolumeClaim(ctx context.Context) error {
	resource, err := g.client.CoreV1().PersistentVolumeClaims(string(g.namespace)).Get(ctx, g.resourceName, metav1.GetOptions{})
	if err != nil {
		return err
	}

	describable, err := ToDescribableResource(resource)
	if err != nil {
		return err
	}

	g.resource = describable
	return nil
}

func (g *getVisitor) visitResourceQuota(ctx context.Context) error {
	resource, err := g.client.CoreV1().ResourceQuotas(string(g.namespace)).Get(ctx, g.resourceName, metav1.GetOptions{})
	if err != nil {
		return err
	}

	describable, err := ToDescribableResource(resource)
	if err != nil {
		return err
	}

	g.resource = describable
	return nil
}

func (g *getVisitor) visitLimitRange(ctx context.Context) error {
	resource, err := g.client.CoreV1().LimitRanges(string(g.namespace)).Get(ctx, g.resourceName, metav1.GetOptions{})
	if err != nil {
		return err
	}

	describable, err := ToDescribableResource(resource)
	if err != nil {
		return err
	}

	g.resource = describable
	return nil
}
